package com.elitel.base.entity.sqlparse;


import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;

import java.util.List;
import java.util.Map;

/**
 * SQL解析器
 *
 * @author xqh
 */
public class SqlParser {

    private final Configuration configuration;

    public SqlParser() {
        configuration = new Configuration();
    }

    /**
     * 解析SQL语句并返回SQL数据
     * 该方法主要用于解析给定的SQL语句，并确保其有效性和安全性通过参数转义处理，它避免了XPath注入的风险
     * 同时，该方法只处理单句SQL语句，如果给定的SQL包含多句，将抛出异常
     *
     * @param sql    待解析的SQL语句
     * @param params SQL语句中参数的映射，用于参数替换
     * @return SqlData对象，包含处理后的SQL语句
     * @throws IllegalArgumentException 如果找不到SQL语句或SQL语句包含多句时抛出
     */
    public SqlData parse(String sql, Map<String, Object> params) {
        boolean staticSql = isStaticSql(sql);
        if (staticSql) {
            if (sql.contains("<=")) {
                sql = sql.replace("<=", "**************************");
            }
            sql = sql.replace("<", " <![CDATA[ < ]]> ");
            sql = sql.replace("**************************", " <![CDATA[ <= ]]> ");
        }
        String xml = "<select>" + sql + "</select>";

        XPathParser parser = new XPathParser(xml);

        // 将XPath表达式抽象为常量
        final String SQL_XPATH = "select|insert|update|delete";
        List<XNode> xNodes = parser.evalNodes(SQL_XPATH);

        if (xNodes == null || xNodes.isEmpty()) {
            throw new IllegalArgumentException("找不到SQL语句");
        }
        if (xNodes.size() > 1) {
            throw new IllegalArgumentException("只能解析一句SQL语句");
        }

        LanguageDriver langDriver = configuration.getLanguageRegistry().getDefaultDriver();

        XNode node = xNodes.get(0);
        SqlSource sqlSource = langDriver.createSqlSource(configuration, node, null);
        BoundSql boundSql = sqlSource.getBoundSql(params);

        SqlData sqlData = new SqlData();
        sqlData.setSql(boundSql.getSql());
        return sqlData;
    }

    public static boolean isStaticSql(String sql) {
        try {
            CCJSqlParserUtil.parse(sql);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
